package org.video.helloworld.fixUtil;

import android.content.Context;
import android.os.Environment;

import androidx.annotation.NonNull;

import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

public class FixDexUtil {
    private static final String DEX_SUFFIX = ".dex";
    private static final String APK_SUFFIX = ".apk";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    public static final String DEX_DIR = "odex";
    private static final String OPTIMIZE_DEX_DIR = "odex";
    private static HashSet<File> loadedDex = new HashSet<>();

    static {
        loadedDex.clear();
    }

    /**
     * 加载补丁，使用默认目录：data/data/包名/files/odex
     */
    public static void loadFixedDex(Context context){
        loadFixedDex(context,null);
    }

    /**
     * 加载补丁文件
     * @param context
     * @param pathFilesDir
     */
    public static void loadFixedDex(Context context, File pathFilesDir) {
        //合并之前的dex
        doDexInject(context,loadedDex);
    }

    public static boolean isGoingToFix(@NonNull Context context){
        boolean canFix = false;
        File externalStorageDirectory = Environment.getExternalStorageDirectory();

        //遍历所有的修复Dex,因为可能是多个dex修复包
        File fileDir = externalStorageDirectory!=null?new File(externalStorageDirectory,"007"):
                new File(context.getFilesDir(),DEX_DIR);//data/data/包名/files/odex
        File[] listFiles = fileDir.listFiles();
        if (listFiles!=null){
            for (File file:listFiles){
                if (file.getName().startsWith("classes") &&
                        (file.getName().endsWith(DEX_SUFFIX)
                                || file.getName().endsWith(APK_SUFFIX)
                                || file.getName().endsWith(JAR_SUFFIX)
                                || file.getName().endsWith(ZIP_SUFFIX))){
                    loadedDex.add(file);
                    canFix = true;
                }
            }
        }
        return canFix;
    }

    private static void doDexInject(Context appContext, HashSet<File> loadedDex) {
        String optimizeDir = appContext.getFilesDir().getAbsolutePath()+File.separator+OPTIMIZE_DEX_DIR;
        //data/data/包名/files/optimize_dex(必须是自己程序下的目录）

        File fopt = new File(optimizeDir);
        if (!fopt.exists()){
            fopt.mkdirs();
        }
        try {
            //1.加载应用程序dex的Loader
            PathClassLoader pathClassLoader = (PathClassLoader) appContext.getClassLoader();
            for (File dex:loadedDex){
                //2.加载指定的修复的dex文件的loader
                DexClassLoader dexClassLoader = new DexClassLoader(
                        dex.getAbsolutePath(),//修复好的dex所在的目录
                        fopt.getAbsolutePath(),//存放dex的解压目录（用于jar、zip、apk格式的补丁）
                        null,//加载dex时所需要的的库
                        pathClassLoader
                );
                //3.开始合并
                //合并的模板是Element[]，重新赋值它的值即可
                /**
                 * BaseDexClassLoader 中有变量：DexPathList pathList
                 * DexPathList中有变量：Element[] dexElements
                 * 依次反射即可
                 * */
                //3.1 准备好pathList的引用
                Object dexPathList = getPathList(dexClassLoader);
                Object pathPathList = getPathList(pathClassLoader);
                //3.2 从pathList中反射出element集合
                Object leftDexElements = getDexElements(dexPathList);
                Object rightDexElements = getDexElements(pathPathList);
                //3.3 合并两个dex数组
                Object dexElements = combineArry(leftDexElements,rightDexElements);

                //重写给PathList里面的Element[] dexElements；赋值
                Object pathList = getPathList(pathClassLoader);
                setField(pathList,pathList.getClass(),"dexElements",dexElements);
            }
        }catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }

    }

    /**
     * 反射给对象中的属性重新赋值
     * @param obj
     * @param cl
     * @param field
     * @param value
     */
    private static void setField(Object obj, Class<?> cl, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field declaredField = cl.getDeclaredField(field);
        declaredField.setAccessible(true);
        declaredField.set(obj,value);
    }

    /**
     * 合并数组
     * @param arrayLhs
     * @param arrayRhs
     * @return
     */
    private static Object combineArry(Object arrayLhs, Object arrayRhs) {
        Class<?> clazz = arrayLhs.getClass().getComponentType();
        int i= Array.getLength(arrayLhs);//新数组长度
        int j = Array.getLength(arrayRhs);//原数组长度
        int k = i+j;
        Object result = Array.newInstance(clazz,k);//创建一个类型为class，长度为k的新数组
        System.arraycopy(arrayLhs,0,result,0,i);
        System.arraycopy(arrayRhs,0,result,i,j);
        return result;
    }

    private static Object getDexElements(Object dexPathList) throws NoSuchFieldException, IllegalAccessException {
        return getField(dexPathList,dexPathList.getClass(),"dexElements");
    }

    /**
     * 反射得到类加载器中的pathList对象
     * @param dexClassLoader
     * @return
     */
    private static Object getPathList(Object dexClassLoader) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        return getField(dexClassLoader,Class.forName("dalvik.system.BaseDexClassLoader"),"pathList");
    }

    /**
     * 反射得到对象中的属性值
     * @param obj
     * @param cl
     * @param field
     * @return
     */
    private static Object getField(Object obj, Class<?> cl, String field) throws NoSuchFieldException, IllegalAccessException {
        Field localField = cl.getDeclaredField(field);
        localField.setAccessible(true);
        return localField.get(obj);
    }
}
